/*
 *  $Id$
 *  Copyright [2010] [Panxiaobo] 
 *  Licensed under the Apache License, Version 2.0 (the "License"); 
 *  you may not use this file except in compliance with the License. 
 *  You may obtain a copy of the License at
 *  
 *     http://www.apache.org/licenses/LICENSE-2.0
 *     
 *  Unless required by applicable law or agreed to in writing, software 
 *  distributed under the License is distributed on an "AS IS" BASIS, 
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 *  See the License for the specific language governing permissions and 
 *  limitations under the License. 
 */
package pxb.openid.op;

import java.io.IOException;
import java.math.BigInteger;
import java.security.KeyPair;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;

import javax.crypto.interfaces.DHPrivateKey;
import javax.crypto.interfaces.DHPublicKey;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import pxb.openid.Association;
import pxb.openid.Constants;
import pxb.openid.OpenIdException;
import pxb.openid.util.Dh;
import pxb.openid.util.Mac;
import pxb.openid.util.Util;

/**
 * 接受RP的Associate,每一个RP连接都会创建一个Association并且由AssociationManager管理
 * 
 * @author Panxiaobo
 * 
 */
public class AssociateAccepter implements Constants {

    /**
     * Openid 2.0 final specification定义的默认DH算法的GENERATOR
     */
    public static final BigInteger DEFAULT_GENERATOR = BigInteger.valueOf(2);

    /**
     * Openid 2.0 final specification定义的默认DH算法的Modulus
     */
    public static final BigInteger DEFAULT_MODULUS = new BigInteger("1551728981814736974712322577637155399157248019669"
            + "154044797077953140576293785419175806512274236981" + "889937278161526466314385615958256881888899512721"
            + "588426754199503412587065565498035801048705376814" + "767265132557470407658574792912915723345106432450"
            + "947150072296210941943497839259847603755949858482" + "53359305585439638443");
    private static final Logger log = LoggerFactory.getLogger(AssociateAccepter.class);

    /**
     * 8.2. Association Session Response
     * 
     * @param connEncrypt
     *            该次连接是否通过加密方式（https）
     * @param values
     *            RP的请求数据
     * @throws IOException
     */
    public Map<String, String> accept(boolean connEncrypt, Map<String, String> values,
            AssociationManager associationManager) {

        // debug输出请求数据
        if (log.isDebugEnabled()) {
            log.debug("OP <=- RP associate request:");
            Map<String, String> map = new TreeMap<String, String>(values);
            for (Map.Entry<String, String> entry : map.entrySet()) {
                log.debug("{} = {}", entry.getKey(), entry.getValue());
            }
            log.debug("==================== End");
        }

        String associateType = values.get(Constants.OPENID_ASSOCIATION_TYPE);
        String sessionType = values.get(Constants.OPENID_SESSION_TYPE);

        // TODO 检查associateType,只能为HMAC-SHA1或者HMAC-SHA256,
        // TODO 检查sessionType，只能为DH-SHA1或者DH-SHA256或者no-encryption,并且与associateType相同

        // 判断是否加密MAC密钥
        boolean encryption = !NO_ENCRYPTION.equals(sessionType);
        boolean dhsha256 = HMAC_SHA256.equals(associateType);
        // 检验sessionType，不允许在未加密的连接使用no-encryption
        if ((!connEncrypt) && (!encryption)) {
            throw new OpenIdException("not support no-encryption in a not security connection");
        }
        // 生成MAC密钥
        byte[] macKey = Mac.generateKeyHmacShax(dhsha256 ? 256 : 1);

        // 建立Association
        Association association = associationManager.build(macKey, associateType);

        // 回复数据
        Map<String, String> rspMap = new HashMap<String, String>();

        // 基本的回复参数
        // 8.2.1. Common Response Parameters
        rspMap.put("ns", OPENID_20_NAMESPACE);
        rspMap.put("assoc_handle", association.getId());
        rspMap.put("session_type", sessionType);
        rspMap.put("assoc_type", associateType);
        // 设置超时时间,单位：秒
        rspMap.put("expires_in", Long.toString((association.getExpireTime().getTime() - new Date().getTime()) / 1000));

        if (!encryption) {
            // 直接传送mac密钥
            // 8.2.2. Unencrypted Response Parameters
            rspMap.put("mac_key", Util.ToBase64(macKey));
        } else {
            // 将mac密钥加密后传输
            String OPENID_DH_CONSUMER_PUBLIC = values.get(Constants.OPENID_DH_CONSUMER_PUBLIC);
            String OPENID_DH_GENERATOR = values.get(Constants.OPENID_DH_GENERATOR);
            String OPENID_DH_MODULUS = values.get(Constants.OPENID_DH_MODULUS);

            // 根据openid 2.0 spec RP可能不发送DH_MODULUS和DH_GENERATOR，使用默认值
            BigInteger p = OPENID_DH_MODULUS == null ? DEFAULT_MODULUS : Util.Base64ToBigInteger(OPENID_DH_MODULUS);
            BigInteger g = OPENID_DH_GENERATOR == null ? DEFAULT_GENERATOR : Util
                    .Base64ToBigInteger(OPENID_DH_GENERATOR);
            BigInteger y2 = Util.Base64ToBigInteger(OPENID_DH_CONSUMER_PUBLIC);

            // 生成DH密钥对
            KeyPair kp = Dh.generateKeyPair(p, g);

            // 加密mac密钥
            byte[] EncodedMac = Util.xorsha(dhsha256 ? 256 : 1, Dh.shared((DHPrivateKey) kp.getPrivate(), y2), macKey);

            BigInteger y1 = ((DHPublicKey) kp.getPublic()).getY();

            // 发送公钥和加密后的mac密钥
            // 8.2.3. Diffie-Hellman Response Parameters
            rspMap.put("dh_server_public", Util.BigIntegerToBase64(y1));
            rspMap.put("enc_mac_key", Util.ToBase64(EncodedMac));
        }

        // debug输送数据
        if (log.isDebugEnabled()) {
            log.debug("OP -=> RP associate response:");
            Map<String, String> map = new TreeMap<String, String>(rspMap);
            for (Map.Entry<String, String> entry : map.entrySet()) {
                log.debug("{} = {}", entry.getKey(), entry.getValue());
            }
            log.debug("==================== End");
        }

        return rspMap;
    }
}
